/*!
 *  Javascript URI Parser and Builder v1.0
 *  http://code.google.com/p/uri/
 *  
 *  Copyright 2010, Li Qiang, liqiang@gmail.com
 *  Dual licensed under the MIT or any OSI licenses.
 */

/**  
 *  URI components:
 *
 *    [scheme:]scheme-specific-part[#fragment] 
 *    [scheme:][//authority][path][?query][#fragment] 
 *    
 *  Please note there are deviations from references; however it should work 
 *  as you would expect for reasonably well-formed urls.
 *  
 *  References:
 *
 *    http://download.oracle.com/javase/1.5.0/docs/api/java/net/URI.html
 *    http://download.oracle.com/javase/1.5.0/docs/api/java/net/URL.html
 *    http://developer.android.com/reference/android/net/Uri.html
 *    http://www.ietf.org/rfc/rfc3986
 *
 *  Credits:
 *
 *    Parser based on Steven Levithan's work http://stevenlevithan.com/demo/parseuri/js/, MIT License
 *     
 */

/*
	URI.parse(str), URI.fromParts(scheme, ssp, fragment), URI.create(obj), URI.fromForm(form, base), URI.resolve(str, base) 
	 
	    Construct URI from string, object, form fields or resolving against a base URI.
	
	URI component accessor and property name:
	
	    source, scheme, schemeSpecificPart, authority, userInfo, user, password, host, port, file, path, directory, basename, query, fragment
	
	    e.g.  getScheme(), setScheme('https') or  scheme(), scheme('https'), to remove: setFragment() or fragment(null)
	
	    Set methods can be chained.   
	
	Query and parameters
	    
	    query(), getQuery(), setQuery(), params(), param(), removeParam()
	
	URI.setAjaxHandler(handler, config)
	 
	    handler(options): an adapter function to Ajax Framework for supporting  Ajax calls (getContent), 
	         this: the calling URI object instance
	         options: set with URI.option(name, value) in addition to components mapped with config
	        
	    config: a set of key/value pairs that map component name to that of the framework; url, params in addition to all component names.
	 
	    Handler and config can be setup globally common to all URI's or individually for each URI instance.

 
	//
	// Example(jQuery)
	//
	
	var base = URI.parse('http://www.google.com/'); // or URI.create({scheme:'http',host:'www.google.com',path:'/'});
	base.resolve('/search')
	.setAjaxHandler(jQuery.ajax, {url:'url', params:'data', user:'username'})
	.option('cache', 'false')
	.option('dataType','html')
	.option('type','GET')
	.option('success', function (data) {
		alert(data);
	})
	.param('hl','en')
	.param('q','kung fu panda')
	.getContent();	
	
	//
	// The following are example adapters for some of the most popular Ajax frameworks.
	//
	
	//jQuery http://api.jquery.com/jQuery.ajax/
	URI.setAjaxHandler(function (options) {
		jQuery.ajax(options);
	}, 
	{url: 'url', params: 'data'}
	);
	URI.parse().option('type', 'POST').option('success', callback).getContent();
	
	//Dojo http://dojotoolkit.org/reference-guide/dojo/xhrGet.html#dojo-xhrget
	URI.setAjaxHandler(function (options) {
		var method = options['method'];
		if (method === 'POST') {
			dojo.xhrPost(options);
		} else {
			dojo.xhrGet(options);
		}
	},
	{url: 'url', params: 'content', user: 'user', password: 'password'}
	);
	URI.parse().option('method', 'POST').option('load', callback).getContent();
	
	//YUI http://developer.yahoo.com/yui/docs/YAHOO.util.Connect.html
	URI.setAjaxHandler(function (options) {
		YAHOO.util.Connect.asyncRequest(options['method'], options['url'], options['callback'], options['query']);
	},{url: 'url', query: 'query'}
	);
	URI.parse().option('method', 'POST').option('callback', callback).getContent();
	
	//MooTools http://mootools.net/docs/core/Request/Request
	URI.setAjaxHandler(function (options) {
		var myRequest = new Request(options);
		myRequest.send();
	}, 
	{url: 'url', query: 'data', user: 'user', password: 'password'}
	);
	URI.parse().option('onSuccess', callback).getContent();
	
	//Prototype http://prototypejs.org/api/ajax/options
	URI.setAjaxHandler(function (options) {
		new Ajax.Request(options);
	}, 
	{url: 'url', params: 'postBody'}
	);
	URI.parse().option('method', 'post').option('onSuccess', callback).getContent();
	
	//Other
	URI.setAjaxHandler(function (options) {
		var uri = this;
		//custom Ajax code
	}
	);
*/

/*jslint regexp: true, undef: true, confusion: true */ 
(function (window, unknown) {
	"use strict";

	var COMPONENTS = ["source", "scheme", "schemeSpecificPart", null, "authority", "userInfo", "user", "password", "host", "port", "file", "path", "directory", "basename", "query", "fragment"],
		SOURCE = 0,
		SCHEME = 1,
		SCHEME_SPECIFIC_PART = 2,
		SLASHSLASH = 3,
		AUTHORITY = 4,
		USER_INFO = 5,
		USER = 6,
		PASSWORD = 7,
		HOST = 8,
		PORT = 9,
		FILE = 10,
		PATH = 11,
		DIRECTORY = 12,
		BASENAME = 13,
		QUERY = 14,
		FRAGMENT = 15,
		PARAMS = 16,
		parser, 
		builder,
		ajaxHandler = (window.jQuery ? window.jQuery.ajax : null),
		ajaxConfig;
	
	// encoder/decoder

	function encode(v) { 
		return (v === null || typeof v === 'undefined' ? '' : encodeURIComponent(v));
	} 

	function decode(v) {
		return (v === null || typeof v === 'undefined' ? '' : decodeURIComponent(v));
	}

	// util

	function slashpath(path, base) {
		var r;
		if (path.charAt(0) === '/') {
			return path;
		}
		r = [];
		base = base || '/';
		r.push(base);
		r.push(path);
		return r.join('');
	}

	function lowerCamelCase() {
		var r  = [], a = arguments, i;
		for (i = 0; i < a.length; i += 1) {
			if (i === 0) {
				r.push(a[0]);
			} else {
				r.push(a[i].charAt(0).toUpperCase());
				r.push(a[i].substring(1));
			}
		}
		return r.join('');
	}
	
	function toString(str) {
		if (typeof str === 'undefined') {
			return null;
		}
		if (str === null) {
			return null;
		}
		if (typeof str === 'string') {
			return str;
		}
		return str.toString();
	}

	function equal(str1, str2) {
		var s1, s2;
		s1 = toString(str1);	
		s2 = toString(str2);
		return s1 === s2;
	}

	function equalIgnoreCase(str1, str2) {
		var s1, s2;
		s1 = toString(str1);
		s2 = toString(str2);
		if (s1 === null && s2 === null) {
			return true;
		}
		if (s1 === null || s2 === null) {
			return false;
		}
		return s1.toLowerCase() === s2.toLowerCase();
	}

	function compare(obj1, obj2) {
		var p;
		if (obj1 === obj2) {
			return true;
		}
		for (p in obj1) {
			if (obj1.hasOwnProperty(p)) {
				if (obj1[p] !== obj2[p]) {
					return false;
				}
			}
		}
		for (p in obj2) {
			if (obj2.hasOwnProperty(p)) {
				if (obj1[p] !== obj2[p]) {
					return false;
				}
			}
		}
		return true;
	}
	
	function buildQuery(params) {
		var r = [], n;
		if (params) {
			for (n in params) {
				if (params.hasOwnProperty(n)) {
					r.push(encode(n) + "=" + encode(params[n]));
				}
			}
		}
		return r.join('&');
	}
	
	function parseQuery(str) {
		var params = {};
		if (str) {
			str.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function (s, n, v) {
				if (s && n) { 
					params[decode(n)] = decode(v); 
				}
			});
		}
		return params;
	}
	
	function serializeForm(form) {
		var els = form.elements, i, j, el, name, r = [];
		for (i = 0; i < els.length; i += 1) {
			el = els[i];
			if (!el.disabled && el.name && el.name.length > 0) {
				name = encode(el.name);
				switch (el.tagName.toLowerCase()) {
				case 'button':
					//discard
					break;
				case 'input':
					switch (el.type) {
					case 'submit':
					case 'reset':
					case 'button':
						//discard
						break;
					case 'text':
					case 'hidden':
					case 'password':
						r.push(name + "=" + encode(el.value));
	                    break;
					case 'checkbox':
					case 'radio':
						if (el.checked) {
							r.push(name + "=" + encode(el.value));
						}
						break;
					case 'file':
						break;
					default:
						r.push(name + "=" + encode(el.value));
					}
					break;
				case 'select':
					if (el.type === 'select-multiple') {
						for (j = 0; j < el.options.length; j += 1) {
							if (el.options[j].selected) {
								r.push(name + "=" + encode(el.options[j].value));
							}
						}
					} else {
						r.push(name + "=" + encode(el.value));
					}
					break;
				case 'textarea':
					r.push(name + "=" + encode(el.value));
					break;
				default:
				}
			}
		}
		return r.join("&");
	}
	
	//
	function setAjaxHandler(handler, config) {
		ajaxHandler = handler;
		ajaxConfig = config;
	}
	
	//
	// Parser
	//
	parser = (function () {	
		/* range, dirty, regex */
		var rules = [
		    [[SOURCE, FRAGMENT], null, /^(?:([^:\/?#]+):)?((?:(\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?))(?:#(.*))?/],
			[[SCHEME, SCHEME], null, null],
			[[SCHEME_SPECIFIC_PART, QUERY], null, /(?:(\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?)(?:#(?:.*))?/],
			null, /* SLASHSLASH */
			[[AUTHORITY, PORT], [SCHEME_SPECIFIC_PART], /^(?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?/],
			[[USER_INFO, PASSWORD], [SCHEME_SPECIFIC_PART, AUTHORITY], /^([^:]*)(?::(.*))?/],
			[[USER, USER], [SCHEME_SPECIFIC_PART, AUTHORITY, USER_INFO], null],
			[[PASSWORD, PASSWORD], [SCHEME_SPECIFIC_PART, AUTHORITY, USER_INFO], null],
			[[HOST, HOST], [SCHEME_SPECIFIC_PART, AUTHORITY], null],
			[[PORT, PORT], [SCHEME_SPECIFIC_PART, AUTHORITY], null],
			[[FILE, QUERY], [SCHEME_SPECIFIC_PART], /^(?:(((?:[^?\/]*\/)*)([^?]*))(?:\?(.*))?)/],
			[[PATH, BASENAME], [SCHEME_SPECIFIC_PART, FILE], /^((?:[^\/]*\/)*)(.*)/], 
			[[DIRECTORY, DIRECTORY], [SCHEME_SPECIFIC_PART, FILE, PATH], null],
			[[BASENAME, BASENAME], [SCHEME_SPECIFIC_PART, FILE, PATH], null],
			[[QUERY, QUERY], [SCHEME_SPECIFIC_PART, FILE], null],
			[[FRAGMENT, FRAGMENT], null, null]
		], parser = {};
		
		function split(re, s) {
			switch (typeof s) {
			case 'string':
				return re.exec(s);
			case 'undefined':
				return [];
			case 'object': /*null array...*/
				if (s === null) {
					return [];
				}
				if (typeof s.length === 'number') {
					return s; //array
				}
				return re.exec(s.toString());
			default: /*boolean number function...*/
				return re.exec(s.toString());
			}
		}
		
		// set components and dirty flags.
		function parse(rule) {
			var re = rule[2], begin = rule[0][0], end = rule[0][1], dirty = rule[1];
			return function (s, components) { 
				var ca = components, m = (re !== null ? split(re, s) : [s]), len = ca.length, i, j;
				ca[SOURCE] = unknown;
				for (i = begin, j = 0; i < len && i <= end; i += 1, j += 1) {
					ca[i] = m[j] || null;
					if (i === QUERY) {
						ca[PARAMS] = parseQuery(ca[i]);
					}
				}
				if (dirty !== null) {
					for (i = 0; i < dirty.length; i += 1) {
						ca[dirty[i]] = unknown;
					}
				}
			};
		}
		
		function init() {
			var i, n, r;
			for (i = 0; i < rules.length; i += 1) {
				n = COMPONENTS[i];
				r = rules[i];
				if (n !== null && r !== null) {
					parser[n] = parse(r);
				}
			}
		}
		
		init();
		
		return parser;
	}());

	//
	// Builder
	// uri ::= source
	// source ::= scheme ':' schemeSpecificPart '#' fragment
	// scheme ::= 'http' | 'https'
	// schemeSpecificPart ::= '//' authority file
	// authority ::= userInfo '@' host ':' port
	// userInfo ::= user ':' password
	// file ::= path '?' query 
	// path ::= directory basename 
	// query ::= params | param*
	// 
	builder = (function () {
		var rules = (function () {
			var rules = [];
			
			function symbol() {
				var args = arguments;
				return function (components) {
					var a = args, c = components[a[0]], r, i, v;
					if (typeof c === 'undefined') {
						//rebuild component if dirty/undefined
						r = [];
						for (i = 1; i < a.length; i += 1) {
							v = a[i](components);
							if (v !== null) {
								r.push(v);
							}
						}
						c = r.length > 0 ? r.join('') : null;
						components[a[0]] = c;
					}
					return c;
				};
			}
			
			function terminal(s) {
				return function (components) { 
					var c = components[s]; 
					if (typeof c === 'undefined') {
						c = null;
					}
					return c;
				};
			}
			
			function group(s1, s2) {
				return function (components) {
					var v1, v2;
					v1 = typeof s1 === 'function' ? s1(components) : s1;
					v2 = typeof s2 === 'function' ? s2(components) : s2;
					if (v1 !== null && v2 !== null) {
						return v1 + v2;
					}
					return null;
				};
			}
			
			function params(s) {
				return function (components) { 
					var c = components[s]; 
					if (typeof c === 'undefined') {
						c = buildQuery(components[PARAMS]);
						components[s] = c;
					}
					return c;
				};
			}
			
			// rules
			rules[USER] = terminal(USER);
			rules[PASSWORD] = terminal(PASSWORD);
			rules[USER_INFO] = symbol(USER_INFO, rules[USER], group(':', rules[PASSWORD]));
			rules[HOST] = terminal(HOST);
			rules[PORT] = terminal(PORT);
			rules[AUTHORITY] = symbol(AUTHORITY, group(rules[USER_INFO], '@'), rules[HOST], group(':', rules[PORT]));
			rules[DIRECTORY] = terminal(DIRECTORY);
			rules[BASENAME] = terminal(BASENAME);
			rules[PATH] = symbol(PATH, rules[DIRECTORY], rules[BASENAME]);
			rules[QUERY] = params(QUERY);
			rules[FILE] = symbol(FILE, rules[PATH], group('?', rules[QUERY]));
			rules[SLASHSLASH] = terminal(SLASHSLASH);
			rules[SCHEME_SPECIFIC_PART] = symbol(SCHEME_SPECIFIC_PART, rules[SLASHSLASH], rules[AUTHORITY], rules[FILE]);
			rules[SCHEME] = terminal(SCHEME);
			rules[FRAGMENT] = terminal(FRAGMENT);
			rules[SOURCE] = symbol(SOURCE, group(rules[SCHEME], ':'), rules[SCHEME_SPECIFIC_PART], group('#', rules[FRAGMENT]));
			
			return rules;
		}()), builder = {};
		
		function build(p) {
			return function (components) { 
				var v = p(components);
				return v;
			};
		}
		
		function init() {
			var i, n, p;
			for (i = 0; i < rules.length; i += 1) {
				n = COMPONENTS[i];
				p = rules[i];
				if (n !== null && p !== null) {
					builder[n] = build(p);
				}
			}
		}
		
		init();
		
		return builder;
	}());
	
	//
	// URI - Uniform Resource Identifier
	//
	
	function URI(arg) {
		var components = []; components[PARAMS] = {};
		
		function dirtyQuery() {
			var dirty = [SOURCE, SCHEME_SPECIFIC_PART, FILE, QUERY], i;
			for (i = 0; i < dirty.length; i += 1) {
				components[dirty[i]] = unknown;
			}
		}
		
		this.settings = {}; /* Ajax request options */
		this.handler = null; /* Ajax handler */
		this.confifg = null; /* Ajax component mapping */

		this.get = function (n) {
			var b = builder[n];
			if (b) {
				return b(components);
			}
			return null;
		};
		
		this.set = function (n, args) {
			var s, p;
			s = (args && args[0] ? args[0] : null);
			p = parser[n];
			if (p) {
				p(s, components);
			}
			return this;
		};
		
		this.params = function (params) {
			if (typeof params === 'undefined') {
				return components[PARAMS];
			} else {
				parser.query(buildQuery(params), components);
				return this;
			}
		};
		
		this.param = function (key, v) {
			if (typeof v === 'undefined') {
				return this.params()[key] || null;
			} else {
				this.params()[key] = toString(v);
				dirtyQuery();
				return this;
			}
		};
		
		this.removeParam = function (key) {
			var p = this.params(), v = p[key] || null;
			delete p[key];
			dirtyQuery();
			return v;
		};
		
		function init(val) {
			var ka, i, n, v;
			val = val || {};
			if (typeof val === 'string') {
				parser.source(val, components);
			} else {
				//order is significant
				ka = COMPONENTS;
				components[SLASHSLASH] = (val.opaque === true ? null : '//');
				for (i = 0; i < ka.length; i += 1) {
					n = ka[i];
					if (n !== null) {
						v = val[n];
						if (typeof v !== 'undefined') {
							parser[n](v, components);
						}
					}
				}
			}
		}
		
		init(arg);
	}
	
	// Creates a URI by parsing the given string
	function parse(str) {
		return new URI(str);
	}

	// Creates a URI from the given components
	function create(obj) {
		obj = (obj && obj.$class === 'URI' ? obj.toString() : obj);
		return new URI(obj);
	}
	
	// Creates a new URI from the components of the given one
	function clone(uri) {
		var s = uri.toString();
		return new URI(s);
	}
	
	// Creates a new URI by parsing the given string and then resolving it against a context URI
	function resolve(val, base) {
		var v, r, p, q, f;
		
		v = (val !== null && typeof val === 'object' ? (val.$class === 'URI' ? val : create(val)) : parse(val));
		if (v.isAbsolute()) {
			return v;
		}
		r = (typeof base === 'object' ? (base.$class === 'URI' ? clone(base) : create(base)) : parse(base));

		p = v.path(); p = p ? slashpath(p, r.directory()) : r.path();
		q = v.query();
		f = v.fragment();

		r.path(p);
		r.query(q);	 
		r.fragment(f);
		
		return r;
	}
	
	// Creates a URI from the given components
	function fromParts(scheme, ssp, fragment) {
		var r, s;
		r = [];
		if (scheme) {
			r.push(scheme);
			r.push(':');
		}
		if (ssp) {
			r.push(ssp);
		}
		if (fragment) {
			r.push('#');
			r.push(fragment);
		}
		s = r.join('');
		return parse(s);
	}

	// Creates a URI from the given form id or element, resolving action against a context URI (default current window location)
	function fromForm(val, base) {
		var d = window.document, f, a, q;
		f = (typeof val === 'string' ? d.getElementById(val) : val);
		a = f.getAttribute('action') || '/';
		q = serializeForm(f);
		base = base || window.location.href;
		return resolve(a, base).query(q);
	}

	URI.prototype = {
		// URI type
		$class: 'URI',
		$version: '1.0.0',
		
		options: function (val) {
			if (typeof val === 'undefined') {
				return this.settings;
			} else {
				this.settings = (val === null ? {} : val);	
				return this;
			}	
		},
		option: function (key, val) {
			if (typeof val === 'undefined') {
				return this.settings[key];
			} else {
				this.settings[key] = val;
				return this;
			}
		},
		removeOption: function (key) {
			var v = this.settings[key];
			delete this.settings[key];
			return v;
		},
		
		setAjaxHandler: function (handler, config) {
			this.handler = handler;
			this.config = config;
			return this;
		},
		getContent: function () {
			var opt = this.settings || {}, ajax = this.handler || ajaxHandler, cfg = this.config || ajaxConfig || {}, c, v;
			if (ajax) {
				//map components to config
				for (c in cfg) {
					if (cfg.hasOwnProperty(c)) {
						v = cfg[c];
						if (v) {
							switch (c) {
							case 'url':
								opt[v] = this.getUrl();
								break;
							case 'params':
								opt[v] = this.params();
								break;
							default:
								opt[v] = this.get(c);
							}
						}
					}
				}
				ajax.call(this, opt);
			}
			return ajax;
		},

		//
		isAbsolute: function () {
			return (this.scheme() ? true : false);
		},
		
		isOpaque: function () {
			return (this.isAbsolute() && (this.schemeSpecificPart() || '').charAt(0) !== '/' ? true : false);
		},
		
		pathSegments: function (val) {
			var p;
			if (val) {
				p =  typeof val.length === 'number' ? val.join('/') : toString(val); 
				this.setPath(p);
				return this;
			} else {
				p = this.getPath();
				return (p !== null ? p.split('/') : []);
			}
		},
		
		// Returns the string representation of this URI removing user, password, query, and fragment
		getUrl: function () {
			var r = [], s, h, n, p;

			s = this.scheme();
			h = this.host();
			n = this.port();
			p = this.path();
			if (s) {
				r.push(s);
				r.push(':');
			}
			if (!this.isOpaque()) {
				r.push('//');
			}
			if (h) {
				r.push(h);
				if (n) {
					r.push(':');
					r.push(n);
				}
			}
			if (p) {
				p = h ? slashpath(p, '/') : p;
				r.push(p);
			}
			return r.join('');
		},

		// Creates a new URI by parsing the given string and then resolving it against this URI
		resolve: function (str) {
			return resolve(str, this);
		},

		// Creates a new URI with the components from this URI.
		buildUpon: function () {
			return this.clone();
		},

		//
		clone: function () {
			return clone(this);
		},
			
		// Returns the string representation of this URI
		toString: function () {
			return this.getSource();
		},

		// Tests this URI for equality with another object
		equals: function (obj) {
			if (typeof obj === 'undefined') {
				return false;
			}
			if (obj === this) {
				return true;
			}
			
			var that = (typeof obj !== 'object' ? parse(obj) : obj); 

			if (!equalIgnoreCase(this.scheme(), that.scheme())) {
				return false;
			}
			// authority
			if (!equal(this.userInfo(), that.userInfo())) {
				return false;
			}
			if (!equalIgnoreCase(this.host(), that.host())) {
				return false;
			}
			if (!equal(this.port(), that.port())) {
				return false;
			}
		
			if (!equal(this.path(), that.path())) {
				return false;
			}
			if (!compare(this.params(), that.params())) {
				return false;
			}
			if (!equal(this.fragment(), that.fragment())) {
				return false;
			}
			return true;
		}
	};
	
	// URI getters and setters, support all names in COMPONENTS
	function init() {
		var i, name, getter, setter;
		
		function accessX(method) {
			return function (val) { 
				return typeof val === 'undefined' ? this.get(method, arguments) : this.set(method, arguments);
			};
		}
		
		function getX(method) {
			return function () { 
				return this.get(method, arguments);
			};
		}
		
		function setX(method) {
			return function () {
				return this.set(method, arguments);
			};
		}
		
		for (i = 0; i < COMPONENTS.length; i += 1) {
			name = COMPONENTS[i];
			if (name !== null) {
				getter = lowerCamelCase('get', name);
				setter = lowerCamelCase('set', name);
	
				URI.prototype[name] = accessX(name);
				URI.prototype[getter] = getX(name);
				URI.prototype[setter] = setX(name);
			}
		}
	}

	init();
	
	//
	// public
	//

	window.URI = {
		setAjaxHandler: setAjaxHandler,
		parse: parse,
		create: create,
		fromParts: fromParts,
		fromForm: fromForm,
		resolve: resolve,
		encode: encode,
		decode: decode
	};
	
}(window));